package weeny.recycle;

import java.awt.BasicStroke;
import java.awt.Dimension;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import prefuse.Constants;
import prefuse.Visualization;
import prefuse.data.Edge;
import prefuse.data.Graph;
import prefuse.data.Node;
import prefuse.data.Tuple;
import prefuse.data.expression.Predicate;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.EdgeRenderer;
import prefuse.render.LabelRenderer;
import prefuse.render.PolygonRenderer;
import prefuse.render.Renderer;
import prefuse.render.ShapeRenderer;
import prefuse.util.GraphicsLib;
import prefuse.util.PrefuseLib;
import prefuse.util.StrokeLib;
import prefuse.visual.EdgeItem;
import prefuse.visual.NodeItem;
import prefuse.visual.VisualGraph;
import prefuse.visual.VisualItem;
import prefuse.visual.VisualTable;
import prefuse.visual.expression.InGroupPredicate;
import prefuse.visual.sort.ItemSorter;
import weeny.pre.util.Geom;
import weeny.pre.util.IntColor;
import weeny.pre.util.Pre;

import static prefuse.render.PolygonRenderer.POLYGON;


@Deprecated
public class MarkerHelper {
	private VisualTable vt;
	private Graph graph;
	private VisualGraph vGraph;
	private String group;
	private String tGroup;
	private String gGroup;
	private Visualization vis;
	private Map<String, Renderer> group2Renderer = new HashMap<String, Renderer>();
	
	public VisualTable getTable(){
		return vt;
	}
	public VisualTable getEdgeTable(){
		return (VisualTable) vGraph.getEdgeTable();
	}
	public void clearAll(){
		graph.clear();
		vt.clear();
	}
	public void dispose(){
		clearAll();
		vis.removeGroup(tGroup);
		vis.removeGroup(gGroup);
		for(String group : group2Renderer.keySet()){
			vis.removeGroup(group);
		}
	}
	public void clear(String group){
		Set<NodeItem> edgeNodeSet = new HashSet<NodeItem>();
		for(Iterator<VisualItem> it = vis.items(group); it.hasNext();){
			VisualItem item = it.next();
			if (item instanceof EdgeItem) {
				EdgeItem edge = (EdgeItem) item;
				edgeNodeSet.add(edge.getSourceItem());
				edgeNodeSet.add(edge.getTargetItem());
				graph.removeEdge((Edge)vis.getSourceTuple(edge));
			}
			else if (item instanceof NodeItem) {
				graph.removeNode((Node) vis.getSourceTuple(item));
			}
			else{
				vt.removeTuple(item);
			}
		}
		for (NodeItem nodeItem : edgeNodeSet) {
			if (nodeItem.getDegree() == 0) {
				graph.removeNode((Node) vis.getSourceTuple(nodeItem));
			}
		}
		vis.getFocusGroup(group).clear();
	}
	public Iterator<VisualItem> tableItems(Predicate filter){
		return vis.items(tGroup, filter);
	}
	private String graphNodeGroup = null;
	public MarkerHelper(Visualization vis, final boolean onTop){
		this.vis = vis;
		group = System.currentTimeMillis()+"";
		tGroup = group+"Table";
		gGroup = group +"Graph";
		graphNodeGroup = gGroup+".nodes";
		graph = new Graph(true);
		vGraph = vis.addGraph(gGroup, graph);
		vis.setVisible(gGroup+".nodes", null, false);
		vt = vis.addTable(tGroup);
		vt.addColumn(POLYGON, float[].class, null);
		vt.addColumn(PrefuseLib.getStartField(POLYGON), float[].class, null);
		vt.addColumn(PrefuseLib.getEndField(POLYGON),   float[].class, null);
		vt.addColumn("text", String.class);
		if (vis.getDisplayCount() == 0) {
			return;
		}
		final ItemSorter sorter = vis.getDisplay(0).getItemSorter();
		vis.getDisplay(0).setItemSorter(new ItemSorter(){
			@Override
			public int score(VisualItem item) {
				if (item.isInGroup(graphNodeGroup)) {
					return 0;
				}
				if (item.isInGroup(tGroup) || item.isInGroup(gGroup)) {
					if (item.isInGroup(tGroup)) {
						return onTop? Integer.MAX_VALUE/2 : Integer.MIN_VALUE/2;
					}
					else{
						return onTop? Integer.MAX_VALUE/2+1 : Integer.MIN_VALUE/2+1;
					}
				}
				else{
					return sorter.score(item);
				}
			}
		});		
	}
	public MarkerHelper(Visualization vis){
		this(vis, true);
	}
	public void addGraphColumn(String name, Class type){
		vis.getSourceData(gGroup).addColumn(name, type);
	}
	public boolean removeEdge(VisualItem item){
		return vis.getSourceData(gGroup).removeTuple(item.getSourceTuple());
	}
	public boolean removeTableItem(VisualItem item){
		return vt.removeTuple(item);
	}
	public boolean hasColumn(String name, Class type){
		return vt.canGet(name, type);
	}
	public void addTableColumn(String name, Class type, Object dft){
		vt.addColumn(name, type, dft);
	}
	
	public void setTableColumnValue(String field, Predicate pred, Object val){
		Iterator<Tuple> it = vt.tuples(vt.rows(pred));
		while(it.hasNext()){
			it.next().set(field,val);
		}
	}
	public EdgeItem markEdge(String group, EdgeItem e, int col){
		return markEdge(group, e.getSourceItem(), e.getTargetItem(), col);
	}
	public EdgeItem markEdge(String group, Line2D l, int col, float size){
		EdgeItem edge = markEdge(group, l.getP1(), l.getP2(), col);
		edge.setSize(size);
//		edge.setStroke(StrokeLib.getStroke(size));
		return edge;
	}
	public EdgeItem markEdge(String group, Line2D l, int col){
		return markEdge(group, l.getP1(), l.getP2(), col);
	}
	public EdgeItem markEdge(String group, Object s, Object e, int col){
		if (vis.getGroup(group) == null) {
			setupRenderer(group, new EdgeRenderer(Constants.EDGE_TYPE_LINE, Constants.EDGE_ARROW_NONE));
		}
		EdgeItem edge = addEdge(s, e);
		vis.getFocusGroup(group).addTuple(edge);
		Pre.setStkCol(edge, col);
		return edge;
	}	
	public VisualItem markText(String group, String text, Object pos){
		if (vis.getFocusGroup(group)==null) {
			setupRenderer(group, new LabelRenderer("text"));
		}
		VisualItem v = vt.addItem();
		Pre.setPos(v, pos);
		v.setTextColor(IntColor.gray(255));
		v.setString("text", text);
		vis.getGroup(group).addTuple(v);
		return v;
	}
	public VisualItem markShape(Object o1, double size, int filCol, int stkCol, int shape, boolean visible){
		throw new UnsupportedOperationException();
//		return markShape(Geom.asPnt(o1).getX(), Geom.asPnt(o1).getY(), size, filCol, stkCol, shape, visible);
	}
	private VisualItem markShape(double x, double y, double size, int filCol, int stkCol, int shape, boolean visible){
		VisualItem v = vt.addItem();
		v.setX(x);
		v.setY(y);
		v.setFillColor(filCol);
		v.setStrokeColor(stkCol);
		v.setShape(shape);
		v.setSize(size);
		v.setVisible(visible);
		return v;
	}
	public VisualItem markShape(String group, double x, double y, double size, int stkCol, int shape){
		if (vis.getGroup(group) == null) {
			setupRenderer(group, new ShapeRenderer(10));
		}
		VisualItem vi = markShape(x, y, size, IntColor.gray(200), stkCol, shape, true);
		vis.getFocusGroup(group).addTuple(vi);
		return vi;
	}
	public VisualItem markShape(String group, Object o1, double size, int stkCol, int shape){
		throw new UnsupportedOperationException();
//		return markShape(group, Geom.asPnt(o1).getX(), Geom.asPnt(o1).getY(), size, stkCol, shape);
	}
	public VisualItem markCurve(String group, float[] xyxy, int col, float size){
		if (vis.getGroup(group) == null) {
			PolygonRenderer pRenderer = new PolygonRenderer();
			pRenderer.setClosePath(false);
			pRenderer.setPolyType(Constants.POLY_TYPE_CURVE);
			setupRenderer(group, pRenderer);
		}
		float[] array = new float[xyxy.length];
		System.arraycopy(xyxy, 0, array, 0, array.length);
		VisualItem vi = vt.addItem();
		vi.set(POLYGON, array);
		Pre.setStkCol(vi, col);
		vi.setStroke(StrokeLib.getStroke(size));
		vis.getFocusGroup(group).addTuple(vi);
		return vi;
	}
	public VisualItem markPoly(String group, float[] xyxy, int col, float size){
		if (vis.getGroup(group) == null) {
			PolygonRenderer pRenderer = new PolygonRenderer();
			pRenderer.setClosePath(false);
			setupRenderer(group, pRenderer);
		}
		float[] array = new float[xyxy.length];
		System.arraycopy(xyxy, 0, array, 0, array.length);
		VisualItem vi = vt.addItem();
		vi.set(POLYGON, array);
		Pre.setStkCol(vi, col);
		vi.setStroke(StrokeLib.getStroke(size));
		vis.getFocusGroup(group).addTuple(vi);
		return vi;
	}
	public VisualItem markBox(String group, Rectangle2D rect, int col, float size){
		return markPoly(group, toPolyArray(rect), col, size);
	}
	public EdgeItem markArrow(String group, Object o1, double dir, double len, int col){
		Point2D s = Geom.toPnt(o1);
		pntA.setLocation(len*Math.cos(dir)+s.getX(), len*Math.sin(dir)+s.getY());
		return markArrow(group, s, pntA, col);
	}
	public List<EdgeItem> markChain(String group, float[] polys, int col){
		if (vis.getGroup(group) == null) {
			setupRenderer(group, new EdgeRenderer(Constants.EDGE_TYPE_LINE, Constants.EDGE_ARROW_NONE));
		}		
		NodeItem source = addNodeItem();
		source.setX(polys[0]);
		source.setY(polys[1]);
		List<EdgeItem> result = new ArrayList<EdgeItem>();
		for (int i = 2; i < polys.length; i += 2) {
			NodeItem target = addNodeItem();
			Pre.setPos(target, polys[i], polys[i+1]);
			EdgeItem edge = (EdgeItem) vGraph.getEdge(graph.addEdge(source.getRow(),target.getRow()));
			edge.setStroke(StrokeLib.getStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
			edge.setStrokeColor(col);
			vis.getFocusGroup(group).addTuple(edge);
			source = target;
			result.add(edge);
		}
		return result;
	}
	private NodeItem addNodeItem(){
		return (NodeItem) vGraph.getNode(graph.addNodeRow());
	}
	public List<EdgeItem> markChain(String group, Point2D s, Point2D e, int num, int col){
		if (vis.getGroup(group) == null) {
			setupRenderer(group, new EdgeRenderer(Constants.EDGE_TYPE_LINE, Constants.EDGE_ARROW_NONE));
		}
		double stepX = s.getX()/num-e.getX()/num;
		double stepY = s.getY()/num-e.getY()/num;
		List<EdgeItem> result = new ArrayList<EdgeItem>();
		NodeItem n1 = (NodeItem) vGraph.getNode(graph.addNodeRow());
		n1.setX(s.getX()); 
		n1.setY(s.getY());
		for(int i = 0; i<num; i++){
			e.setLocation(s.getX()-stepX, s.getY()-stepY);
			NodeItem n2 = (NodeItem) vGraph.getNode(graph.addNodeRow());
			n2.setX(e.getX()); 
			n2.setY(e.getY());
			EdgeItem edge = (EdgeItem) vGraph.getEdge(graph.addEdge(n1.getRow(), n2.getRow()));
			edge.setStrokeColor(col);
			edge.setFillColor(col);
			edge.setStroke(StrokeLib.getStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
			vis.getFocusGroup(group).addTuple(edge);
			result.add(edge);
			n1 = n2;
			s.setLocation(e.getX(), e.getY());
		}
		return result;
	}
	public EdgeItem markArrow(String group, Object obj, int col, float size){
		EdgeItem arrow = markArrow(group, obj, col);
		arrow.setSize(size);
//		arrow.setStroke(StrokeLib.getStroke(size));
		return arrow;
	}
	
	public EdgeItem markArrow(String group, Object obj, int col){
		throw new UnsupportedOperationException();
//		if (obj instanceof EdgeItem) {
//			EdgeItem new_name = (EdgeItem) obj;
//			return markArrow(group, 
//					Geom.asPnt(Geom.src(new_name)), 
//					Geom.asPntB(Geom.src(new_name)), col);
//		}
//		else if (obj instanceof Line2D){
//			Line2D line = (Line2D) obj;
//			return markArrow(group, line.getP1(), line.getP2(), col);
//		}
//		throw new IllegalArgumentException("input should be EdgeItem or Line2D");
	}
	public EdgeItem markArrow(String group, Object o1, Object o2, int col){
		throw new UnsupportedOperationException();
//		return markArrow(group, Geom.asPnt(o1), Geom.asPntB(o2), col);
	}
	public EdgeItem markArrow(String group, Point2D s, Point2D e, int col){
		if (vis.getGroup(group) == null) {
			setupRenderer(group, new EdgeRenderer(Constants.EDGE_TYPE_LINE, Constants.EDGE_ARROW_FORWARD));
		}
		EdgeItem edge = addEdge(s, e);
		vis.getFocusGroup(group).addTuple(edge);
		edge.setStrokeColor(col);
		edge.setFillColor(col);
		return edge;
	}
	private EdgeItem addEdge(Object s, Object e){
		NodeItem n1 = (NodeItem) vGraph.getNode(graph.addNodeRow());
		Pre.setPos(n1, s);
		NodeItem n2 = (NodeItem) vGraph.getNode(graph.addNodeRow());
		Pre.setPos(n2, e);
		return (EdgeItem) vGraph.getEdge(graph.addEdge(n1.getRow(), n2.getRow()));
	}
	public VisualItem markRect(String group, Rectangle2D rect, int fil, int stk){
		VisualItem vi = vt.addItem();
		if (vis.getGroup(group) == null) {
//			vis.addFocusGroup(group);
			setupRenderer(group, new PolygonRenderer(Constants.POLY_TYPE_LINE));
		}

		vis.getFocusGroup(group).addTuple(vi);
		vi.set(POLYGON, toPolyArray(rect));
		vi.setStrokeColor(stk);
		vi.setFillColor(fil);
		return vi;
	}
	private void setupRenderer(String group, Renderer renderer) {
		if (vis.getGroup(group)==null) {
			vis.addFocusGroup(group);	
			InGroupPredicate pred = new InGroupPredicate(group);
			group2Renderer.put(group, renderer);
			DefaultRendererFactory drf = (DefaultRendererFactory) vis.getRendererFactory();
			drf.add(pred, renderer);
		}
	}
	public static Rectangle2D boundaryOf(Iterator<VisualItem> it){
		Rectangle2D rect = null;
		while(it.hasNext()){
			VisualItem item = it.next();
			if (rect == null) {
				rect = new Rectangle2D.Double(item.getX(), item.getY(), 0,0);
			}
			else{
				rect.add(item.getX(), item.getY());
			}
		}
		return rect;
	}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
	
	
	public static Rectangle2D[][] divideRect(Rectangle2D rect, int wCnt, int hCnt){
		Rectangle2D[][] rects = new Rectangle2D[hCnt][wCnt];
		double height = rect.getHeight()/hCnt;
		double width  = rect.getWidth() /wCnt;
		double newX   = rect.getX();
		double newY   = rect.getY();
		for(int h = 0; h< hCnt; h++){
			newX = rect.getX();
			for (int w = 0; w < wCnt; w++) {
				rects[h][w] = new Rectangle2D.Double(newX, newY, width, height);
				newX += width;
			}
			newY += height;
		}
		return rects;
	}
	public static  float[] toPolyArray(List list){
		throw new UnsupportedOperationException();
//		float[] array = new float[list.size()*2];
//		for (int i = 0; i<list.size(); i++){
//			array[i*2]= (float) Geom.asPnt(list.get(i)).getX();
//			array[i*2+1] = (float) Geom.asPnt(list.get(i)).getY();
//		}
//		return array;
	}
	public static  float[] toPolyArray(Rectangle2D rect){
		assert rect != null;
		float[] array = new float[10];
		array[0] = (float) rect.getX();
		array[1] = (float) rect.getY();
		
		array[2] = (float) (rect.getMaxX());
		array[3] = (float) rect.getY();
		
		array[4] = (float) (rect.getMaxX());
		array[5] = (float) (rect.getMaxY());
		
		array[6] = (float) rect.getX();
		array[7] = (float) (rect.getMaxY());
		
		array[8] = array[0];
		array[9] = array[1];
		return array;
	}
	public static Rectangle2D expand(Dimension r, double margin){
		Rectangle2D rect = new Rectangle2D.Double(
				-margin,               -margin,
				r.getWidth()+margin*2, r.getHeight()+margin*2);
		return rect;
	}
	public static Rectangle2D expand(Rectangle2D r, double margin){
		Rectangle2D rect = new Rectangle2D.Double(
				r.getX()-margin,       r.getY()-margin,
				r.getWidth()+margin*2, r.getHeight()+margin*2);
		return rect;
	}
	public static float[] centerOf(Collection<VisualItem> cluster){
		float[] xyxy = null;
		for (VisualItem visualItem : cluster) {
			float[] poly = (float[]) visualItem.get(POLYGON);
			if (xyxy == null) {
				xyxy = new float[poly.length];
				System.arraycopy(poly, 0, xyxy, 0, xyxy.length);
			}
			else{
				for(int i =0; i<poly.length; i++){
					xyxy[i] +=poly[i];
				}
			}
		}
		for(int i = 0; i<xyxy.length; i++){
			xyxy[i]/=cluster.size();
		}
		return xyxy;
	}
	public static double[] xComponent(Object xyxy) {
		return xComponent((float[]) xyxy);
	}
	public static double[] xComponent(float[] xyxy){
		double[] xxxx = new double[xyxy.length / 2];
		for (int i = 0; i < xyxy.length / 2; ++i) {
			xxxx[i] = xyxy[i*2];
		}
		return xxxx;
	}
	public static double[] yComponent(Object xyxy) {
		return yComponent((float[]) xyxy);
	}

	public static double[] yComponent(float[] xyxy) {
		double[] yyyy = new double[xyxy.length / 2];
		for (int i = 0; i < xyxy.length / 2; ++i) {
			yyyy[i] = xyxy[i * 2 + 1];
		}
		return yyyy;
	}
	private static Point2D   pntA = new Point2D.Double();
	private static Point2D   pntB = new Point2D.Double();
	private static Point2D[] pnts = new Point2D[]{new Point2D.Double(), 
		                                          new Point2D.Double()};
	private static Line2D    line = new Line2D.Double();
	public static double lengthInRect(Object o, Rectangle2D rect){
		return Math.sqrt(lengthInRectSq(o, rect));
	}
	public static double lengthInRectSq(Object o, Rectangle2D rect){
		if (o instanceof Line2D) {
			Line2D l = (Line2D) o;
			pntA.setLocation(l.getP1());
			pntB.setLocation(l.getP2());
			line.setLine(l);
		}
		else if (o instanceof EdgeItem) {
			EdgeItem new_name = (EdgeItem) o;
			pntA.setLocation(Geom.srcX(new_name), Geom.srcY(new_name));
			pntB.setLocation(Geom.tarX(new_name), Geom.tarY(new_name));
			line.setLine(pntA, pntB);
		}
		if (rect.contains(pntA) && rect.contains(pntB)){
			return pntA.distanceSq(pntB);
		}
		else if (rect.contains(pntA) || rect.contains(pntB)){
			int cnt = GraphicsLib.intersectLineRectangle(line, rect, pnts);
			if (cnt == 1) {
				if (rect.contains(pntA)) {
					return (pnts[0].distanceSq(pntA));
				} else if (rect.contains(pntB)) {
					return pnts[0].distanceSq(pntB);
				}
				else{
					throw new IllegalStateException("impossible");
				}
			}
		}
		else{
			int cnt = GraphicsLib.intersectLineRectangle(line, rect, pnts);
			if (cnt == 2) {
				return pnts[0].distanceSq(pnts[1]);
			}
		}
		return 0;	
	}
	public static double lengthInRectSq(float[] poly, Rectangle2D rect, int xIdx){
		if (xIdx % 2 != 0) {
			throw new IllegalStateException("idx should start from X");
		}
		if (xIdx + 4 > poly.length) {
			throw new IllegalStateException("idx should not be the last");
		}
		pntA.setLocation(poly[xIdx+0], poly[xIdx+1]);
		pntB.setLocation(poly[xIdx+2], poly[xIdx+3]);
		line.setLine(pntA, pntB);
		return lengthInRectSq(line, rect);
	}
	public static double lengthInRect(float[] poly, Rectangle2D rect, int xIdx){
		return Math.sqrt(lengthInRectSq(poly, rect, xIdx));
	}
	public static Line2D asSegment(double[] poly, int xIdx){
		line.setLine(poly[xIdx+0], poly[xIdx+1], poly[xIdx+2], poly[xIdx+3]);
		return line;
	}
	public static Line2D asSegment(float[] poly, int xIdx){
		line.setLine(poly[xIdx+0], poly[xIdx+1], poly[xIdx+2], poly[xIdx+3]);
		return line;
	}
	public static int getXIndex(double[] poly, double x){
		if (x >= poly[poly.length-2]) {
			return poly.length-4;
		}
		for(int i = 0; i<poly.length-2; i+=2){
			if (x>=poly[i] && x<poly[i+2]) {
				return i;
			}
		}
		throw new IllegalArgumentException("x value is too big");
	}
	public static int getXIndex(float[] poly, double x){
		if (x >= poly[poly.length-2]) {
			return poly.length-4;
		}
		for(int i = 0; i<poly.length-2; i+=2){
			if (x>=poly[i] && x<poly[i+2]) {
				return i;
			}
		}
		throw new IllegalArgumentException("x value is too big");
	}
	public static double getYValue(double[] poly, double x){
		int xIdx = getXIndex(poly, x);
		Line2D line = asSegment(poly, xIdx);
		return line.getY1()+(x-line.getX1())/(Geom.deltaX(line))*Geom.deltaY(line);
	}
	public static double getYValue(float[] poly, double x){
		int xIdx = getXIndex(poly, x);
		Line2D line = asSegment(poly, xIdx);
		return line.getY1()+(x-line.getX1())/(Geom.deltaX(line))*Geom.deltaY(line);
	}
	public static double stepX(Line2D line, double step){
		double length = Geom.length(line);
		return step/length*Geom.deltaX(line);
	}
	public static double distanceSq(VisualItem item1, VisualItem item2){
		return distanceSq((float[])item1.get(POLYGON), (float[])item2.get(POLYGON));
	}
	public static double distanceSq(double[] ds1, double[] ds2){
		double result = 0;
		for (int i = 0; i < ds1.length; i++) {
			result += (ds1[i] - ds2[i]) * (ds1[i] - ds2[i]);
		}
		return result;
	}
	public static double distance(double[] ds1, double[] ds2){
		return Math.sqrt(distanceSq(ds1, ds2));
	}
	public static double distanceSq(float[] ds1, float[] ds2){
		double result = 0;
		for (int i = 0; i < ds1.length; i++) {
			result += (ds1[i] - ds2[i]) * (ds1[i] - ds2[i]);
		}
		return result;
	}
	public static double distance(float[] ds1, float[] ds2){
		return Math.sqrt(distanceSq(ds1, ds2));
	}
	public static boolean intersects(float[] poly, Line2D line){
		for(int i =0; i<poly.length-2; i+=2){
			if( asSegment(poly, i).intersectsLine(line))
				return true;
		}
		return false;
	}
	public static Point2D turningPnt(VisualItem vi, int idx){
		float[] poly = (float[]) vi.get(POLYGON);
		Point2D pnt = new Point2D.Double();
		pnt.setLocation(poly[idx*2], poly[idx*2+1]);
		return pnt;
	}
}
